<?php

namespace plugin\csauto\app\utils;

/**
 * Created by 无所谓已
 * User: Administrator
 * Date: 2025-08-18
 * Time: 13:01
 */
class TorrentDownloader
{
    private $savePath;
    private $timeout;
    private $debug;

    public function __construct($savePath = '/torrents/', $timeout = 30, $debug = false)
    {
        $this->savePath = rtrim($savePath, '/') . '/';
        $this->timeout = $timeout;
        $this->debug = $debug;

        // 确保保存目录存在
        if (!is_dir($this->savePath)) {
            mkdir($this->savePath, 0755, true);
        }
    }

    public function download($url, $header = array())
    {
        // 使用 cURL 下载
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');

        if (!empty($header)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        }

        $response = curl_exec($ch);

        if (curl_error($ch)) {
            $error = curl_error($ch);
            curl_close($ch);
            addlog('TorrentDownloader', "下载失败: {$error}", 'error');
            throw new Exception('下载失败: ' . $error);
        }

        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $headers = substr($response, 0, $headerSize);
        $data = substr($response, $headerSize);

        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
        $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
        curl_close($ch);

        // if ($this->debug) {
        //     addlog('TorrentDownloader', [
        //         'HTTP状态码' => $httpCode,
        //         'Content-Type' => $contentType,
        //         '最终URL' => hideSensitiveUrl($effectiveUrl),
        //         '响应头' => $headers,
        //         '数据前100字符' => substr($data, 0, 100)
        //     ], 'debug');
        // }

        if ($httpCode !== 200) {
            addlog('TorrentDownloader', "HTTP错误: {$httpCode}", 'error');
            if ($httpCode == 404) {
                return [
                    'http_code' => $httpCode
                ];
            }
            throw new Exception('HTTP错误: ' . $httpCode);
        }

        // 解析文件名
        $filename = md5($this->parseFilename($headers, $effectiveUrl, $url) . time()) . ".torrent";

        // 检查是否可能下载了错误页面
        if ($this->looksLikeErrorPage($data)) {
            addlog('downTorrent', "看起来下载的是错误页面而不是torrent文件", 'error');
            return [
                'http_code' => 298
            ];
            throw new Exception('看起来下载的是错误页面而不是torrent文件');
        }

        // 保存文件（即使可能不是有效的torrent文件，也先保存以便调试）
        $fullPath = $this->savePath . $filename;
        if (file_put_contents($fullPath, $data) === false) {
            addlog('downTorrent', "保存文件失败: ", 'error');
            throw new Exception('保存文件失败');
        }

        // 验证 torrent 文件
        if (!$this->isValidTorrent($data)) {
            addlog('downTorrent', "不是有效的 torrent 文件。文件已保存到: {$fullPath}，请检查文件内容。", 'error');
            if ($httpCode == 200) {
                if (preg_match('/invalid downhash/i', $data)) {
                    return [
                        'http_code' => 299
                    ];
                }
            }
            throw new Exception('不是有效的 torrent 文件。文件已保存到: ' . $fullPath . '，请检查文件内容。');
        }

        return [
            'http_code' => $httpCode,
            'filename' => $filename,
            'path' => $fullPath,
            'size' => filesize($fullPath),
            'url' => $url,
            'effective_url' => $effectiveUrl,
            'content_type' => $contentType
        ];
    }

    private function parseFilename($headers, $effectiveUrl, $originalUrl)
    {
        // 1. 从 Content-Disposition 头获取文件名
        if (preg_match('/Content-Disposition:.*filename[^;=\n]*=((["\']).*?\2|[^;\n]*)/i', $headers, $matches)) {
            $filename = trim($matches[1], '"\'');
            // URL 解码文件名
            $filename = urldecode($filename);
            addlog('TorrentDownloader', "解析文件名: {$filename}", 'debug');
            return $filename;
        }

        // 2. 从最终 URL 中提取文件名
        $filename = basename(parse_url($effectiveUrl, PHP_URL_PATH));
        if ($this->isValidFilename($filename)) {
            return $filename;
        }

        // 3. 从原始 URL 中提取文件名
        $filename = basename(parse_url($originalUrl, PHP_URL_PATH));
        if ($this->isValidFilename($filename)) {
            return $filename;
        }

        // 4. 从查询参数中查找文件名
        parse_str(parse_url($effectiveUrl, PHP_URL_QUERY), $queryParams);
        foreach (['name', 'filename', 'file', 'dn'] as $param) {
            if (isset($queryParams[$param]) && $this->isValidFilename($queryParams[$param])) {
                return $queryParams[$param];
            }
        }

        // 5. 如果还是没有有效文件名，使用时间戳
        return 'torrent_' . time() . '.torrent';
    }

    private function isValidFilename($filename)
    {
        return !empty($filename) &&
            $filename !== '/' &&
            !preg_match('/\.(php|html?|asp|jsp)$/i', $filename) &&
            strlen($filename) < 255;
    }

    private function looksLikeErrorPage($data)
    {
        // 首先检查是否是torrent文件
        if (substr($data, 0, 11) === 'd8:announce') {
            // 这是一个torrent文件，不是错误页面
            return false;
        }

        $dataSample = substr($data, 0, 500);

        // 检查是否为HTML页面
        if (preg_match('/<html/i', $dataSample) || preg_match('/<head/i', $dataSample)) {
            // 只有在确认是HTML页面时才检查错误相关关键词
            $errorPatterns = [
                '/(404|not found|page not found)/i',
                '/(error|错误)/i',
                '/(forbidden|拒绝访问)/i'
            ];

            foreach ($errorPatterns as $pattern) {
                if (preg_match($pattern, $dataSample)) {
                    return true;
                }
            }
        }

        return false;
    }

    private function isValidTorrent($data)
    {
        // 更宽松的 torrent 文件验证
        if (strlen($data) < 10) { // torrent 文件通常不会这么小
            return false;
        }

        // torrent 文件是以 "d" 开头的 bencode 编码数据
        if ($data[0] !== 'd') {
            return false;
        }

        // 检查是否包含 torrent 关键字段
        $torrentIndicators = ['announce', 'info', 'name', 'piece'];
        $dataSample = substr($data, 0, min(1000, strlen($data)));

        $matches = 0;
        foreach ($torrentIndicators as $indicator) {
            if (strpos($dataSample, $indicator) !== false) {
                $matches++;
            }
        }

        return $matches >= 2; // 至少包含2个关键字段
    }

    // 添加一个跳过验证直接下载的方法
    public function downloadWithoutValidation($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');

        $response = curl_exec($ch);

        if (curl_error($ch)) {
            curl_close($ch);
            throw new Exception('下载失败: ' . curl_error($ch));
        }

        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $headers = substr($response, 0, $headerSize);
        $data = substr($response, $headerSize);

        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
        curl_close($ch);

        if ($httpCode !== 200) {
            throw new Exception('HTTP错误: ' . $httpCode);
        }

        // 解析文件名
        $filename = $this->parseFilename($headers, $effectiveUrl, $url);

        // 保存文件
        $fullPath = $this->savePath . $filename;
        if (file_put_contents($fullPath, $data) === false) {
            throw new Exception('保存文件失败');
        }

        return [
            'filename' => $filename,
            'path' => $fullPath,
            'size' => filesize($fullPath),
            'url' => $url,
            'effective_url' => $effectiveUrl,
            'validated' => $this->isValidTorrent($data)
        ];
    }
}

